package com.salmon.rpc;

import com.esotericsoftware.kryo.Kryo;
import com.esotericsoftware.kryo.io.Input;
import com.esotericsoftware.kryo.io.Output;
import com.esotericsoftware.kryo.pool.KryoPool;
import com.esotericsoftware.kryo.serializers.JavaSerializer;
import com.salmon.rpc.utils.KryoUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * <p>
 * RPC 框架
 * </p>
 */
public class RpcFramework {

    /**
     * 暴露服务
     *
     * @param service 服务实现
     * @param port 服务端口
     * @throws Exception
     */
    public static void export(final Object service, int port) throws Exception {
        if (service == null)
            throw new IllegalArgumentException("service instance == null");
        if (port <= 0 || port > 65535)
            throw new IllegalArgumentException("Invalid port " + port);
        System.out.println("Export service " + service.getClass().getName() + " on port " + port);
        ServerSocket serverSocket = new ServerSocket(port);
        while (true) {
            try (Socket socket = serverSocket.accept()) {
                try (Input input = new Input(socket.getInputStream())) {
                    KryoPool pool = KryoUtils.newKryoPool();
                    Kryo kryo = pool.borrow();
                    RpcModel model = kryo.readObject(input, RpcModel.class, new JavaSerializer());
                    Output output = new Output(socket.getOutputStream());
                    try {
                        Method method = service.getClass().getMethod(model.getMethod(), model.getParameterTypes());
                        Object result = method.invoke(service, model.getArgs());
                        kryo.writeObject(output, result, new JavaSerializer());
                    } catch (Throwable t) {
                        kryo.writeObject(output, t, new JavaSerializer());
                    } finally {
                        pool.release(kryo);
                        output.close();
                    }
                }
            }
        }
    }

    /**
     * 引用服务
     *
     * @param <T> 接口泛型
     * @param interfaceClass 接口类型
     * @param host 服务器主机名
     * @param port 服务器端口
     * @return 远程服务
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public static <T> T refer(final Class<T> interfaceClass, final String host, final int port) throws Exception {
        if (interfaceClass == null)
            throw new IllegalArgumentException("Interface class == null");
        if (!interfaceClass.isInterface())
            throw new IllegalArgumentException("The " + interfaceClass.getName() + " must be interface class!");
        if (host == null || host.length() == 0)
            throw new IllegalArgumentException("Host == null!");
        if (port <= 0 || port > 65535)
            throw new IllegalArgumentException("Invalid port " + port);
        System.out.println("Get remote service " + interfaceClass.getName() + " from server " + host + ":" + port);
        return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, (proxy, method, args) -> {
            try(Socket socket = new Socket(host, port)) {
               try(Output out = new Output(socket.getOutputStream())) {
                   KryoPool pool = KryoUtils.newKryoPool();
                   Kryo kryo = pool.borrow();
                   RpcModel rpcModel = new RpcModel();
                   rpcModel.setParameterTypes(method.getParameterTypes());
                   rpcModel.setArgs(args);
                   rpcModel.setMethod(method.getName());
                   kryo.writeObject(out, rpcModel, new JavaSerializer());
                   try(Input input = new Input(socket.getInputStream())) {
                       Object result = kryo.readObject(input, Object.class, new JavaSerializer()); // 读取远程方法调用返回的结果
                       if (result instanceof Throwable) {
                           throw (Throwable) result;
                       }
                       return result;
                   } finally {
                       pool.release(kryo);
                   }
               }
            }
        });
    }
}
